﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Genuilder.Extensibility;
using System.Runtime.Serialization;
using Genuilder.Extensibility.NRefactory;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.NRefactory.Ast;

namespace Genuilder.Extensions
{
	[DataContract]
	public class StronglyTypedReflectionExtension : IExtension
	{
		class Visitor : AbstractAstVisitor
		{
			private CodeWriter writer;

			public Visitor(CodeWriter writer)
			{
				this.writer = writer;
			}

			public override object VisitUsingDeclaration(UsingDeclaration usingDeclaration, object data)
			{
				writer.WriteUsing("System.Reflection");
				return base.VisitUsingDeclaration(usingDeclaration, data);
			}

			public override object VisitUsing(ICSharpCode.NRefactory.Ast.Using @using, object data)
			{
				writer.WriteUsing(@using.Name);
				return base.VisitUsing(@using, data);
			}

			public override object VisitNamespaceDeclaration(ICSharpCode.NRefactory.Ast.NamespaceDeclaration namespaceDeclaration, object data)
			{
				using(writer.WriteNamespace(namespaceDeclaration.Name))
				{
					return base.VisitNamespaceDeclaration(namespaceDeclaration, data);
				}
			}

			public override object VisitTypeDeclaration(ICSharpCode.NRefactory.Ast.TypeDeclaration typeDeclaration, object data)
			{
				using(writer.WriteClass(typeDeclaration.Name + "Type", false, true))
				{
					_Types.Push(typeDeclaration);
					var properties = typeDeclaration.Children.OfType<PropertyDeclaration>().ToList();
					if(properties.Count > 0)
					{
						using(writer.WriteClass("Properties", false, true))
						{
							foreach(var prop in properties)
							{
								prop.AcceptVisitor(this, null);
							}
						}
					}
					var methodGroups = typeDeclaration.Children.OfType<MethodDeclaration>().GroupBy(o => o.Name).ToList();
					if(methodGroups.Count > 0)
					{
						using(writer.WriteClass("Methods", false, true))
						{
							foreach(var methods in methodGroups)
							{
								VisitMethodsDeclaration(methods, null);
							}
						}
					}
					var events = typeDeclaration.Children.OfType<EventDeclaration>().ToList();
					if(events.Count > 0)
					{
						using(writer.WriteClass("Events", false, true))
						{
							foreach(var @event in events)
							{
								@event.AcceptVisitor(this, null);
							}
						}
					}
					_Types.Pop();
					return null;
				}
			}

			Stack<TypeDeclaration> _Types = new Stack<TypeDeclaration>();
			TypeDeclaration CurrentType
			{
				get
				{
					return _Types.Peek();
				}
			}

			public override object VisitPropertyDeclaration(ICSharpCode.NRefactory.Ast.PropertyDeclaration propertyDeclaration, object data)
			{
				if(propertyDeclaration.Modifier.HasFlag(Modifiers.Public))
				{
					writer.Write("public static PropertyInfo " + propertyDeclaration.Name);
					writer.NewLine();
					using(writer.WriteBrackets())
					{
						writer.Write("get");
						writer.NewLine();
						using(writer.WriteBrackets())
						{
							writer.Write("return typeof(" + CurrentType.Name + ").GetProperty(\"" + propertyDeclaration.Name + "\", typeof(" + propertyDeclaration.TypeReference.ToString() + "));");
						}
					}
				}
				return null;
			}

			public override object VisitEventDeclaration(EventDeclaration eventDeclaration, object data)
			{
				if(eventDeclaration.Modifier.HasFlag(Modifiers.Public))
				{
					writer.Write("public static EventInfo " + eventDeclaration.Name);
					writer.NewLine();
					using(writer.WriteBrackets())
					{
						writer.Write("get");
						writer.NewLine();
						using(writer.WriteBrackets())
						{
							writer.Write("return typeof(" + CurrentType.Name + ").GetEvent(\"" + eventDeclaration.Name + "\");");
						}
					}
				}
				return null;
			}

			public void VisitMethodsDeclaration(IGrouping<string, MethodDeclaration> methods, object data)
			{
				bool isOnly = methods.Count() == 0;
				foreach(var method in methods)
				{
					if(method.Modifier.HasFlag(Modifiers.Public))
					{
						writer.Write("public static MethodInfo " + method.Name + (isOnly ? "" : ParametersToName(method)));
						writer.NewLine();
						using(writer.WriteBrackets())
						{
							writer.Write("get");
							writer.NewLine();
							using(writer.WriteBrackets())
							{
								writer.Write("return typeof(" + CurrentType.Name + ").GetMethod(\"" + method.Name + "\", new Type[] { "
									+
									String.Join(", ", method.Parameters.Select(p => "typeof(" + p.TypeReference.ToString() + ")").ToArray())
									+ " });");
							}
						}
					}
				}
			}

			private string ParametersToName(MethodDeclaration method)
			{
				StringBuilder builder = new StringBuilder();
				foreach(var param in method.Parameters)
				{
					var name = param.TypeReference.GetNameWithoutNamespace().ToCharArray();
					if(param.TypeReference.IsArrayType)
					{
						name = (param.TypeReference.Type.WithoutNamespace() + "s").ToCharArray();
					}
					name[0] = char.ToUpper(name[0]);
					builder.Append("_");
					builder.Append(name);
				}
				return builder.ToString();
			}
		}
		#region IExtension Members

		[DataMember]
		public string[] Files
		{
			get;
			set;
		}

		[DataMember]
		public FileQuery FileQuery
		{
			get;
			set;
		}

		public void Execute(ExtensionContext extensionContext)
		{
			var fileQuery = FileQuery ?? new FileQuery();
			fileQuery.SelectByNames(Files);
			foreach(var item in extensionContext.GenItems.GetByQuery(fileQuery).Where(i => i.Modified))
			{
				var asyncItem = item.Children.CreateNew("Reflection" + item.File.Name);
				using(var fs = asyncItem.Open())
				{
					CodeWriter writer = new CodeWriter(fs);
					var itemAst = item.GetExtension<CompilationUnitExtension>();
					itemAst.ParseMethodBodies = false;
					itemAst.CompilationUnit.AcceptVisitor(new Visitor(writer), null);
					writer.Flush();
				}
			}
		}

		#endregion
	}
}
